﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using iNet.Collections;

namespace iNet
{
	/// <summary>
	/// 提供型別相關功能。
	/// </summary>
	public static class TypeUtility
	{
		#region Cast
		/// <summary>
		/// 將指定的物件轉換成指定型別的物件。
		/// </summary>
		/// <typeparam name="T">轉換的目標型別。</typeparam>
		/// <param name="obj">指定的來源物件。</param>
		/// <returns>轉換後的物件。</returns>
		/// <exception cref="InvalidCastException">無效的型別轉換。</exception>
		public static T Cast<T>(this object obj)
		{
			T value;
			if (TryCast<T>(obj, out value))
				return value;
			throw new InvalidCastException();
		}
		/// <summary>
		/// 將指定的物件轉換成指定型別的物件。
		/// </summary>
		/// <param name="obj">指定的來源物件。</param>
		/// <param name="destType">指定轉換的型別。</param>
		/// <returns>轉換後的物件。</returns>
		/// <exception cref="ArgumentNullException">destType 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="InvalidCastException">無效的型別轉換。</exception>
		public static object Cast(this object obj, Type destType)
		{
			object value;
			if (TryCast(obj, destType, out value))
				return value;
			throw new InvalidCastException();
		}
		#endregion
		#region CastToArray
		/// <summary>
		/// 將物件轉換成指定型別的一維陣列。
		/// </summary>
		/// <typeparam name="T">指定的陣列元素型別。</typeparam>
		/// <param name="obj">指定的物件。</param>
		/// <returns>轉換後的一維陣列。</returns>
		/// <exception cref="InvalidCastException">轉換失敗。</exception>
		public static T[] CastToArray<T>(this object obj)
		{
			T[] array;
			if (TryCastToArray<T>(obj, out array))
				return array;
			throw new InvalidCastException();
		}
		#endregion
		#region IsNullable
		static bool IsNullable(Type type)
		{
			string name = type.FullName;
			return (name.Length > 19 && name.Substring(0, 17) == "System.Nullable`1");
		}
		#endregion
		#region TryCast
		/// <summary>
		/// 嘗試將指定的物件轉換成指定型別的物件。
		/// </summary>
		/// <typeparam name="T">轉換的目標型別。</typeparam>
		/// <param name="obj">指定的來源物件。</param>
		/// <param name="value">轉換後的物件。</param>
		/// <returns>True 表示轉換成功，False 表示失敗。</returns>
		public static bool TryCast<T>(this object obj, out T value)
		{
			object convertedObj;
			if (TryCast(obj, typeof(T), out convertedObj))
			{
				value = (T)convertedObj;
				return true;
			}
			value = default(T);
			return false;
		}
		/// <summary>
		/// 嘗試將指定的物件轉換成指定型別的物件。
		/// </summary>
		/// <param name="obj">指定的來源物件。</param>
		/// <param name="destType">指定轉換的型別。</param>
		/// <param name="value">轉換後的物件。</param>
		/// <returns>True 表示轉換成功，False 表示失敗。</returns>
		/// <exception cref="ArgumentNullException">destType 為 Null (Visual Basic 為 Nothing)。</exception>
		public static bool TryCast(this object obj, Type destType, out object value)
		{
			//確認參數
			if (destType == null)
				throw new ArgumentNullException("destType");

			//取得資料
			Type srcType = (obj != null ? obj.GetType() : null);

			//轉換成為 Null
			if (srcType == null)
			{
				//指定結果
				value = null;

				//轉成 Null 參考或 Nullable
				if (!destType.IsValueType || IsNullable(destType))
					return true;

				//轉換失敗
				return false;
			}

			//直接轉換
			if (destType.IsAssignableFrom(srcType))
			{
				value = obj;
				return true;
			}

			//轉成 Nullable
			if (IsNullable(destType))
			{
				Type genericType = destType.GetGenericArguments()[0];
				if (TryCast(obj, genericType, out value))
					return true;
			}

			//使用 IConvertable 轉換
			TypeCode destTypeCode = Type.GetTypeCode(destType);
			IConvertible convertable = (obj as IConvertible);
			try
			{
				switch (destTypeCode)
				{
					case TypeCode.Boolean:
						if (convertable != null)
						{
							value = convertable.ToBoolean(null);
							return true;
						}
						break;
					case TypeCode.Byte:
						if (convertable != null)
						{
							value = convertable.ToByte(null);
							return true;
						}
						break;
					case TypeCode.Char:
						if (convertable != null)
						{
							value = convertable.ToChar(null);
							return true;
						}
						break;
					case TypeCode.DateTime:
						if (convertable != null)
						{
							value = convertable.ToDateTime(null);
							return true;
						}
						break;
					case TypeCode.Decimal:
						if (convertable != null)
						{
							value = convertable.ToDecimal(null);
							return true;
						}
						break;
					case TypeCode.Double:
						if (convertable != null)
						{
							value = convertable.ToDouble(null);
							return true;
						}
						break;
					case TypeCode.Int16:
						if (convertable != null)
						{
							value = convertable.ToInt16(null);
							return true;
						}
						break;
					case TypeCode.Int32:
						if (convertable != null)
						{
							value = convertable.ToInt32(null);
							return true;
						}
						break;
					case TypeCode.Int64:
						if (convertable != null)
						{
							value = convertable.ToInt64(null);
							return true;
						}
						break;
					case TypeCode.SByte:
						if (convertable != null)
						{
							value = convertable.ToSByte(null);
							return true;
						}
						break;
					case TypeCode.Single:
						if (convertable != null)
						{
							value = convertable.ToSingle(null);
							return true;
						}
						break;
					case TypeCode.String:
						value = obj.ToString();
						return true;
					case TypeCode.UInt16:
						if (convertable != null)
						{
							value = convertable.ToUInt16(null);
							return true;
						}
						break;
					case TypeCode.UInt32:
						if (convertable != null)
						{
							value = convertable.ToUInt32(null);
							return true;
						}
						break;
					case TypeCode.UInt64:
						if (convertable != null)
						{
							value = convertable.ToUInt64(null);
							return true;
						}
						break;
					case TypeCode.Object:
						if (convertable != null)
						{
							value = convertable.ToType(destType, null);
							return true;
						}
						break;
				}
			}
			catch
			{ }

			//轉成 URational
			if (destType == typeof(URational))
			{
				//由浮點數轉換
				if (obj is double)
				{
					value = URational.FromDouble((double)obj);
					return true;
				}
				if (obj is float)
				{
					value = URational.FromSingle((float)obj);
					return true;
				}

				//由字串轉換
				if (obj is string)
				{
					URational rational;
					if (URational.TryParse(obj as string, out rational))
					{
						value = rational;
						return true;
					}
				}

				//由整數轉換
				uint intValue;
				if (TryCast<uint>(obj, out intValue))
				{
					value = (new URational(1, intValue));
					return true;
				}
			}

			//轉成 Rational
			if (destType == typeof(Rational))
			{
				//由浮點數轉換
				if (obj is double)
				{
					value = Rational.FromDouble((double)obj);
					return true;
				}
				if (obj is float)
				{
					value = Rational.FromSingle((float)obj);
					return true;
				}

				//由字串轉換
				if (obj is string)
				{
					Rational rational;
					if (Rational.TryParse(obj as string, out rational))
					{
						value = rational;
						return true;
					}
				}

				//由整數轉換
				int intValue;
				if (TryCast<int>(obj, out intValue))
				{
					value = new Rational(1, intValue);
					return true;
				}
			}

			//使用目標型別轉換子轉換
			if (TryCastUsingCastOperator(obj, srcType, destType, destType.GetMethods(BindingFlags.Public | BindingFlags.Static), out value))
				return true;

			//使用來源型別轉換子轉換
			if (TryCastUsingCastOperator(obj, srcType, destType, srcType.GetMethods(BindingFlags.Public | BindingFlags.Static), out value))
				return true;

			//轉換失敗
			value = null;
			return false;
		}
		#endregion
		#region TryCastToArray
		/// <summary>
		/// 嘗試將物件轉換成指定型別的一維陣列。
		/// </summary>
		/// <typeparam name="T">指定的陣列元素型別。</typeparam>
		/// <param name="obj">指定的物件。</param>
		/// <param name="array">轉換後的一維陣列。</param>
		/// <returns>True 表示轉換成功，False 表示轉換失敗。</returns>
		public static bool TryCastToArray<T>(this object obj, out T[] array)
		{
			//確認參數
			if (obj == null)
			{
				array = null;
				return true;
			}

			//轉成陣列
			if (obj is Array)
				return (obj as Array).TryConvertTo<T>(out array);
			array = null;
			return false;
		}
		#endregion
		#region TryCastUsingCastOperator
		static bool TryCastUsingCastOperator(object obj, Type srcType, Type destType, MethodInfo[] methods, out object value)
		{
			for (int i = methods.Length - 1; i >= 0; --i)
			{
				MethodInfo method = methods[i];
				if ((method.Name == "op_Implicit" || method.Name == "op_Explicit") && method.ReturnType == destType)
				{
					ParameterInfo[] paramInfos = method.GetParameters();
					if (paramInfos.Length == 1 && paramInfos[0].ParameterType.IsAssignableFrom(srcType))
					{
						try
						{
							value = method.Invoke(null, new object[] { obj });
							return true;
						}
						catch
						{ }
					}
				}
			}
			value = null;
			return false;
		}
		#endregion
	}
}
